﻿#region

using System;
using System.Collections.Generic;
using System.Linq;
using Tateeda.ClinicalStudy.Base.Data.DBModel;
using Tateeda.ClinicalStudy.Base.Data.EF;
using Tateeda.ClinicalStudy.Base.Modules.Common.Enumeration;
using Tateeda.ClinicalStudy.Base.Modules.Common.Extenstions;
using Tateeda.ClinicalStudy.Base.Modules.Infrastructure.Interfaces.Services;
using Tateeda.ClinicalStudy.Base.Modules.Lookups;
using Tateeda.ClinicalStudy.Base.Modules.Models.Medication;
using Tateeda.ClinicalStudy.Base.Modules.Models.Study;
using Tateeda.ClinicalStudy.Base.Modules.Models.StudyForm;
using Tateeda.ClinicalStudy.Base.Modules.Models.StudyVisit;
using Tateeda.ClinicalStudy.Base.Modules.Models.User;

#endregion

#region - Legal -

///<legal>
/// Copyright © 2011, Tateeda Media Network
/// All rights reserved. http://tateeda.com
///
/// Redistribution and use in source and binary forms, with or without
/// modification, are permitted provided that the following conditions
/// are met:
///
/// - Redistributions of source code must retain the above copyright
/// notice, this list of conditions and the following disclaimer.
///
/// - Neither the name of the Tateeda Media Network, nor the names of its
/// contributors may be used to endorse or promote products
/// derived from this software without specific prior written
/// permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
/// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
/// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
/// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
/// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
/// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
/// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
/// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
/// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
/// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
/// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
/// POSSIBILITY OF SUCH DAMAGE.
///</legal>

#endregion - Legal -

namespace Tateeda.ClinicalStudy.Base.Modules.Infrastructure.Concrete.Services {

    public class StudyService : IStudyService {
        private readonly IAppointmentService _appointmentService = new AppointmentService();
        private readonly IMedicationService _medService = new MedicationService();
        private readonly ISubjectService _subjectService = new SubjectService();
        private readonly IUnitOfWork _unitOfWork;

        private readonly IUserService _userService = new UserService();

        public StudyService() {
            _unitOfWork = new SqlUnitOfWork();
        }

        #region IStudyService Members

        public virtual IUnitOfWork UnitOfWork {
            get { return _unitOfWork; }
        }

        public virtual int AddNewDrug(DrugModel drug) {
            return _medService.AddNewDrug(drug);
        }

        public virtual void AddNewDrugCategory(DrugCategoryModel drugCategory) {
            _medService.AddNewDrugCategory(drugCategory);
        }

        public virtual void AddNewDrugClass(DrugClassModel drugClass) {
            _medService.AddNewDrugClass(drugClass);
        }

        public virtual ICollection<SubjectDrugModel> AddSubjectDrug(SubjectDrugModel subjectDrug) {
            return _medService.AddSubjectDrug(subjectDrug);
        }

        public virtual ICollection<SubjectDrugModel> AddSubjectMedicationHistory(SubjectDrugModel subjectDrug) {
            return _medService.AddSubjectMedicationHistory(subjectDrug);
        }

        public virtual ICollection<RecreationalDrugOrSubstanceModel> AddSubstance(RecreationalDrugOrSubstanceModel substance) {
            return _medService.AddSubstance(substance);
        }

        public virtual void DeleteDrug(int drugId) {
            _medService.DeleteDrug(drugId);
        }

        public virtual void DeleteDrugCategory(int drugCategoryId) {
            _medService.DeleteDrugCategory(drugCategoryId);
        }

        public virtual void DeleteDrugClass(int drugClassId) {
            _medService.DeleteDrugClass(drugClassId);
        }

        /// <summary>
        ///   IMPORTANT: Call it only once before Saving/Updating form.
        ///   Deletes the form answers.
        /// </summary>
        /// <param name = "formId">The form id.</param>
        /// <param name = "apid">The appointment id.</param>
        public virtual void DeleteFormAnswers(int formId, int apid) {
            var apptForm = UnitOfWork.AppointmentForms.Where(ap => ap.AppointmentId == apid && ap.FormId == formId).SingleOrDefault();
            //Delete all OLD Questions in case of an update
            if (apptForm != null) {
                var delAnswers = UnitOfWork.FormAnswers.Where(fa => fa.AppointmentFormId == apptForm.AppointmentFormId).ToList();
                foreach (var delAnswer in delAnswers) {
                    UnitOfWork.FormAnswers.DeleteObject(delAnswer);
                }
                UnitOfWork.Commit();
            }
        }

        public virtual void DeleteSubjectDrug(int subjectDrugId) {
            _medService.DeleteSubjectDrug(subjectDrugId);
        }

        public virtual void DeleteSubstance(int substanceId) {
            _medService.DeleteSubstance(substanceId);
        }

        public virtual ICollection<SubjectDrugModel> EditSubjectsDrug(SubjectDrugModel subjectDrug) {
            return _medService.EditSubjectsDrug(subjectDrug);
        }

        public virtual void EditSubstance(RecreationalDrugOrSubstanceModel substance) {
            _medService.EditSubstance(substance);
        }

        public virtual ICollection<AppointmentModel> FindAppointmentsForDates(DateTime from, DateTime to, int siteId,
                                                                       int appUserId, bool showForAll = false) {
            return _appointmentService.FindAppointmentsForDates(from, to, siteId, appUserId, showForAll);
        }

        public virtual DrugModel FindDrugByName(string drugName) {
            return _medService.FindDrugByName(drugName);
        }

        public virtual ICollection<DrugModel> FindNonPsychoDrugsByName(string drugName) {
            return _medService.FindNonPsychoDrugsByName(drugName);
        }

        public virtual AppointmentModel GetAppointment(int appointmentId) {
            var model = _appointmentService.GetAppointment(appointmentId);
            model.Subject = _subjectService.GetSubject(model.SubjectId);
            var drugsList = _subjectService.GetSubjectDrugPerAppointment(appointmentId, true);

            foreach (var item in _subjectService.GetSubjectDrugPerAppointment(appointmentId))
                drugsList.Add(item);

            foreach (var dr in drugsList)
                model.Subject.SubjectDrugs.Add(dr);

            return model;
        }

        public virtual AppointmentFormModel GetAppointmentForm(int appointmentId, int formId) {
            var apptForm = UnitOfWork.AppointmentForms.Where(af => af.AppointmentId == appointmentId && af.FormId == formId).SingleOrDefault();
            return apptForm.ToAppointmentFormModel();
        }

        public virtual AppointmentFormModel GetAppointmentForm(int appointmentFormId) {
            return UnitOfWork.AppointmentForms.Where(af => af.AppointmentFormId == appointmentFormId).SingleOrDefault().ToAppointmentFormModel();
        }

        public virtual ICollection<StudyFormModel> GetAppointmentForms(int appointmentId, int? status) {
            return _subjectService.GetAppointmentForms(appointmentId, status);
        }

        public virtual ICollection<StudyFormModel> GetAppointmentFormsAll(int appointmentId) {
            return _appointmentService.GetAppointmentFormsAll(appointmentId);
        }

        public virtual ICollection<StudyFormModel> GetAppointmentFormsForStatus(int appointmentId, int statusId) {
            return _appointmentService.GetAppointmentFormsForStatus(appointmentId, statusId);
        }

        public virtual AppUserModel GetAppUser(int appUserId) {
            return _userService.GetUser(appUserId);
        }

        public virtual AppUserModel GetAppUserByLoginName(string userName) {
            return _userService.FindUserByName(userName);
        }

        public virtual AppUserModel GetAppUserByName(string userName) {
            return _userService.FindUserByName(userName);
        }

        public virtual DrugModel GetDrug(int drugId) {
            return _medService.GetDrug(drugId);
        }

        public virtual ICollection<DrugCategoryModel> GetDrugCategories() {
            return _medService.GetDrugCategories();
        }

        public virtual DrugCategoryModel GetDrugCategory(int categoryId) {
            return _medService.GetDrugCategory(categoryId);
        }

        public virtual DrugClassModel GetDrugClass(int drugClassId) {
            return _medService.GetDrugClass(drugClassId);
        }

        public virtual ICollection<DrugClassModel> GetDrugClasses(int categoryId) {
            return _medService.GetDrugClassesForCategory(categoryId);
        }

        public virtual ICollection<DrugClassModel> GetDrugClassesForCategory(int categoryId) {
            return _medService.GetDrugClassesForCategory(categoryId);
        }

        public virtual ICollection<DrugModel> GetDrugsByClass(int classId) {
            return _medService.GetDrugsByClass(classId);
        }

        public virtual ICollection<DrugModel> GetDrugsByClass(int classId, ref int totalCount, int pageIndex = 0,
                                                       int pageSize = 20) {
            return _medService.GetDrugsByClass(classId, ref totalCount, pageIndex, pageSize);
        }

        /// <summary>
        ///   Gets the first visit.
        ///   Assumption is that the first vit is a visit: with min sort order and DayFrom = 0 in the first Arm
        /// </summary>
        /// <returns></returns>
        public virtual VisitModel GetFirstVisit() {
            const int firstArmId = 1;
            var visit = UnitOfWork.Visits.Where(v => v.DayFrom == 0 &&
                                                       v.DayTo == 0 &&
                                                       v.ArmId == firstArmId &&
                                                       v.Status == (int)StudyStatus.ActiveStatus).
                OrderBy(v => v.SortOrder).
                OrderBy(v => v.VisitId).
                First();

            return visit.ToVisitModel();
        }

        public virtual ICollection<FormAnswerModel> GetFormAnswers(int formId, int appointementFormId) {
            var query = (from fa in UnitOfWork.FormAnswers
                         join q in UnitOfWork.Questions
                             on fa.QuestionId equals q.QuestionId
                         where q.FormId == formId && fa.AppointmentFormId == appointementFormId
                         select fa).ToList();

            return query.Select(fa => fa.ToFormAnswerModel()).ToList();
        }

        public virtual ICollection<FormAnswerModel> GetFormAnswers(int formId, int appointementFormId, int questionId) {
            var query = (from fa in UnitOfWork.FormAnswers
                         join q in UnitOfWork.Questions
                             on fa.QuestionId equals q.QuestionId
                         where
                             q.FormId == formId && fa.AppointmentFormId == appointementFormId &&
                             fa.QuestionId == questionId
                         select fa).ToList();

            return query.Select(fa => fa.ToFormAnswerModel()).ToList();
        }

        public virtual StudyFormModel GetFormWithAnswers(int formId, int subjectId) {
            var form = from f in UnitOfWork.Forms
                       where f.FormId == formId
                       select f;
            var frm = form.SingleOrDefault();
            var studyForm = frm.ToFormModel();

            if (frm != null) studyForm.Questions = frm.Questions; //questions;

            return studyForm;
        }

        public virtual ICollection<SubjectModel> GetSubjectsForDurg(int drugId) {
            return _medService.GetSubjectsForDurg(drugId);
        }

        public virtual ICollection<RecreationalDrugOrSubstanceModel> GetSubjectSubstancesPerAppointment(int subjectId,
                                                                                                 int appointementId) {
            return _subjectService.GetSubjectSubstancesPerAppointment(subjectId, appointementId);
        }

        public virtual ICollection<RecreationalDrugOrSubstanceModel> GetSubstances(int subjectId) {
            return _medService.GetSubstances(subjectId);
        }

        /// <summary>
        ///   IMPORTANT: DeleteFormAnswers MUST be called before saving the form answers. We do not have update for this table, only delete and insert.
        ///   Trigger may be added in DB to trace changes if needed.
        ///   Saves the form answers.
        /// </summary>
        /// <param name = "formId">The form id.</param>
        /// <param name = "apid">The appointment id.</param>
        /// <param name = "questionId">The question id.</param>
        /// <param name = "answers">The answers - array of answers IDs or FREE text in case of text field or date field.</param>
        /// <param name = "appUser">The app user.</param>
        public virtual void SaveFormAnswers(int formId, int apid, string questionId, string[] answers,
                                            AppUserModel appUser) {
            var apptForm =
                UnitOfWork.AppointmentForms.Where(ap => ap.AppointmentId == apid && ap.FormId == formId).SingleOrDefault
                    ();
            if (apptForm == null) {
                apptForm = new AppointmentForm {
                    FormId = formId,
                    AppointmentId = apid,
                    Status = StudyStatus.InProgressStatus.ToStatusInt(),
                    CreatedOn = DateTime.UtcNow,
                    CreatedBy = appUser.UserName
                };
                UnitOfWork.AppointmentForms.AddObject(apptForm);
                UnitOfWork.Commit();
            }

            var frmAnswer = new FormAnswer {
                AppointmentFormId = apptForm.AppointmentFormId,
                CreatedOn = DateTime.UtcNow,
                UpdatedOn = DateTime.UtcNow,
                QuestionId = int.Parse(questionId)
            };

            //Check Question type:

            var answer = frmAnswer;
            var question = UnitOfWork.Questions.Where(q => q.QuestionId == answer.QuestionId).SingleOrDefault();
            if (question != null) {
                int questionTypeId = question.QuestionTypeId;
                //If this is not null then it is a text/date type
                bool isTextOrDate = (FormsLookup.QuestionTypeLookup.
                                        Where(
                                            qt => qt.Text.ToLower().Contains("text") ||
                                                  qt.Text.ToLower().Contains("date")
                                        )).
                                        Where(
                                            v => v.Value.ToString() == questionTypeId.ToString()
                                        ).SingleOrDefault() != null;

                if (isTextOrDate) {
                    var firstOrDefault =
                        UnitOfWork.Answers.Where(a => a.QuestionId == frmAnswer.QuestionId).FirstOrDefault();
                    if (firstOrDefault != null)
                        frmAnswer.AnswerId = firstOrDefault.AnswerId;
                    frmAnswer.FreeTextAnswer = answers.Count() > 0 ? answers[0] : null;
                    //do not insert default values
                    if (frmAnswer.AnswerId > 0)
                        UnitOfWork.FormAnswers.AddObject(frmAnswer);
                } else {
                    foreach (string ansId in answers) {
                        int aId = int.Parse(ansId);
                        frmAnswer = new FormAnswer {
                            AppointmentFormId = apptForm.AppointmentFormId,
                            CreatedOn = DateTime.UtcNow,
                            UpdatedOn = DateTime.UtcNow,
                            QuestionId = int.Parse(questionId),
                            AnswerId = aId
                        };

                        //do not insert default values
                        if (frmAnswer.AnswerId > 0)
                            UnitOfWork.FormAnswers.AddObject(frmAnswer);
                    }
                }
            }

            UnitOfWork.Commit();
        }

        public virtual void SetAppointmentFormStatus(int appointementFormId, int status, AppUserModel appUser) {
            var apptForm =
                UnitOfWork.AppointmentForms.Where(af => af.AppointmentFormId == appointementFormId).SingleOrDefault();
            if (apptForm != null) {
                apptForm.Status = status;
                apptForm.UpdatedOn = DateTime.UtcNow;
                apptForm.UpdatedBy = appUser.UserName;
                UnitOfWork.Commit();
            }
        }

        public virtual ICollection<SubjectDrugModel> StopPrescriptionDrug(int subjectDrugId, DateTime stopDate) {
            return _medService.StopPrescriptionDrug(subjectDrugId, stopDate);
        }

        public SubjectDrugModel GetSubjectsDrug(int subjectDrugId) {
            return _medService.GetSubjectsDrug(subjectDrugId);
        }

        public void EditDrugName(int drugId, string name) {
            _medService.EditDrugName(drugId, name);
        }

        #endregion
    }
}